import queue
import pykabqueue

MAX_ERRORS = 3

class MessageError(Exception):
    pass

class CriticalError(Exception):
    pass 

def enqueue_with_error(msg, error):
    """ Puts a failed message back in the queue with the lowest priority.
        If a task fails MAX_ERRORS times, it is put in 
        queue with priority 0 which is never processed.
    """
    failures = msg['failures'] if 'failures' in msg else 0
    errors = msg['errors'] if 'errors' in msg else []
    errors.append(str(error))
    failures += 1
    priority = 10 if (failures < MAX_ERRORS) else 0
    msg.update({
        'priority': priority,
        'errors': errors,
        'failures': failures
    })

    if pykabqueue.logger:
        pykabqueue.logger.error("%s failed" % msg['action'])
    
    # Send failure notification if a notification observer is defined
    if priority == 0 and pykabqueue.failure_notification_observer:
        pykabqueue.failure_notification_observer(msg, error)

    send(msg)
        
# Infers a Processor's name from the action
def processor(action):
    """ Finds the appropriate module in the pykabqueue.processors dict
        and returns it. (The module must implement a 'process' function)
    """
    return __import__(pykabqueue.processors[action])

def process(msg):
    """ Calls the process function of the appropriate processor. If a 
        transient (or non-fatal) error occurs, it's caught and the message 
        requeued. CriticalErrors cause no further action to be taken.
    """
    try:
        processor(msg['action']).process(msg)
    except CriticalError:
        pass
    except Exception, error:
        enqueue_with_error(msg, error)
    
def send(**options):
    """ Puts message in the queue. Priority defaults to 5.
        Must have an action=ACTION parameter.
    """
    if 'action' not in options:
        raise MessageError    
    priority = options['priority'] if 'priority' in options else 5
    queue.enqueue(priority, options)
    